/******************************************************************************/
package jamie.org.sil.www.UsxVernacularCreator;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.NoSuchElementException;


/******************************************************************************/
/**
 * An abstract class implementations of which provide information specific to
 * the XML file we are dealing with.
 * <p>
 *
 * This one class contains <i>all</i> the application-specific information, so
 * by creating different concrete versions of this class, we should be able to
 * tailor the tool for use with other files quite easily.  The relevant version
 * is instantiated in UsxConfigEditorApp.java, based on a command-line
 * parameter, which it passes to <i>createInstance</i>.
*/

public abstract class XmlDetails
{
  /****************************************************************************/
  /****************************************************************************/
  /**                                                                        **/
  /**                               Public                                   **/
  /**                                                                        **/
  /****************************************************************************/
  /****************************************************************************/


  /****************************************************************************/
  /** Creates the relevant instance of the derived class so that we can use
   *  the <i>instance</i> method to obtain the correct instance.
   *
   * @param className "XmlDetails" is prepended to this to give the name of the
   *                  class which we wish to instantiate.
   *
   * @throws RuntimeException
   */

  static public void createInstance (String className) throws RuntimeException
  {
    try
    {
      m_Instance =
       (XmlDetails)
         Class.forName("jamie.org.sil.www.UsxVernacularCreator.XmlDetails" + className).newInstance();
      }
    catch (Exception e)
    {
      throw new RuntimeException(e);
    }


  }


  /****************************************************************************/
  /** Singleton.
   *
   * @return Singleton instance.
   */

  static public XmlDetails instance ()
  {
    return m_Instance;
  }


  /****************************************************************************/
  /** Clear the dirty flag in all tags. */

  public void clearDirty ()
  {
    for (ConfigTag.Generic t : m_OverallTagTable.values()) t.setDirty(false);
  }


  /****************************************************************************/
  /** Returns details of any standard header information to be written out.
   *
   * @return Header information.
   */

  public String getHeader () { return ""; }


  /****************************************************************************/
  /**
   * Returns an iterator which runs over all tags in the order in which
   * they were created.
   *
   * @return Iterator.
   */

  public iterator getIterator ()
  {
    return new iterator();
  }


  /****************************************************************************/
  /** Returns the path to the root of the production folder structure. */

  public String getProductionRootPath () { return m_ProductionRootPath; }


  /****************************************************************************/
  /** Returns the name of the root tag to be used when writing the output file.
   *
   * @return Root tag.
   */

  abstract public String getRootTag ();


  /****************************************************************************/
  /** Gets the row and tab where a given tag appears.
   *
   * @param tag
   * @return Tab and row.
   * @throws RuntimeException
   */

  int[] getTabAndRow (ConfigTag.Generic tag) throws RuntimeException
  {
    int res[] = new int[2];

    for (int tab = 0; tab < m_PerTabTagTable.size(); ++ tab)
    {
      ArrayList<ConfigTag.Generic> tags = m_PerTabTagTable.get(tab);
      for (int row = 0; row < tags.size(); ++row)
        if (tag == tags.get(row))
        {
          res[0] = tab;
          res[1] = row;
          return res;
        }
      }

    throw new RuntimeException("getTabAndRow: Can't find tag");
  }


  /****************************************************************************/
  /** Gets the names to be applied to the tabs on the tabbed pane.
   *
   * @return names
   */

  abstract public String[] getTabNames ();


  /****************************************************************************/
  /** Returns the tag with a given name.
   *
   * @param tagName
   *
   * @return Tag.
   */

  public ConfigTag.Generic getTag (String tagName)
  {
    return m_OverallTagTable.get(tagName);
  }


  /****************************************************************************/
  /** Returns the n'th tag associated with a given tab.
   *
   * @param tabNo
   * @param ix Index.
   *
   * @return Tag.
   */

  public ConfigTag.Generic getTag (int tabNo, int ix)
  {
    return m_PerTabTagTable.get(tabNo).get(ix);
  }


  /****************************************************************************/
  /** Gets a count of the number of tags associated with a given GUI pane.
   *
   * @param tabNo
   *
   * @return Count.
   */

  public int getTagCount (int tabNo)
  {
    return m_PerTabTagTable.get(tabNo).size();
  }


  /****************************************************************************/
  /** Returns details of any standard trailer information to be written out.
   *
   * @return Trailer information.
   */

  public String getTrailer () { return ""; }


  /****************************************************************************/
  /** Returns details of any validation errors.
   *
   * @return Validation errors.
   */

  public String getValidationErrors () { return m_ValidationErrors; }


  /****************************************************************************/
  /** Carries out any initialisation which needs to occur <i>after</i> the
   *  instance has been created.
   */

  public void initialise () {}


  /****************************************************************************/
  /** Checks if the internal data structure is dirty.

      @return True if dirty.
   */

  public boolean isDirty ()
  {
    for (ConfigTag.Generic t : m_OverallTagTable.values())
      if (t.isDirty()) return true;
    return false;
  }


  /****************************************************************************/
  /** Populates all tags from a given config file.
   *
   * @param configFile Config file class from which data is obtained.
   */

  public void populate (XmlFile configFile)
  {
    for (ConfigTag.Generic tag : m_OverallTagTable.values())
    {
      tag.populate(configFile);
      tag.setDirty(false);
    }
  }


  /****************************************************************************/
  /** Saves the path to the root of the production folder structure. */

  public void setProductionRootPath (String s) { m_ProductionRootPath = s; }


  /****************************************************************************/
  /**
   * Validates the current output.  (No need to check that mandatory items have
   * been supplied -- that's handled elsewhere.)
   *
   * @return A tag to which focus is transferred if an error is detected.
   */

  abstract public ConfigTag.Generic validate ();



  /****************************************************************************/
  public class iterator implements Iterator<ConfigTag.Generic>
  {
    public iterator ()
    {
      String s = m_TagList.replaceAll(";+", ";");
      if (s.startsWith(";")) s = s.substring(1);
      if (s.endsWith(";")) s = s.substring(0, s.length());
      m_Tags = s.split(";");
    }

    public boolean hasNext () { return m_Ix < m_Tags.length; }

    public ConfigTag.Generic next ()
    {
      if (m_Ix >= m_Tags.length) throw new NoSuchElementException();
      return m_OverallTagTable.get((String) m_Tags[m_Ix++]);
    }

    public void remove () { throw new UnsupportedOperationException(); }

    private int m_Ix = 0;
    private String[] m_Tags = null;
  }





  /****************************************************************************/
  /****************************************************************************/
  /**                                                                        **/
  /**                              Protected                                 **/
  /**                                                                        **/
  /****************************************************************************/
  /****************************************************************************/

  /****************************************************************************/
  /* Adds a new tag to the internal data structures. */

  protected ConfigTag.Generic addTag (ConfigTag.Generic tag)
  {
    m_PerTabTagTable.get(tag.getTabNo()).add(tag);
    m_OverallTagTable.put(tag.getTagName(), tag);
    m_TagList += ";" + tag.getTagName();
    return tag;
  }


  /****************************************************************************/
  /* Arranges that future tags will be recorded as appearing on a given tab. */

  protected void setTabNo (String tabName) throws RuntimeException
  {
     String tabNames[] = getTabNames();
     for (int i = 0; i < tabNames.length; ++i)
       if (tabName.equalsIgnoreCase(tabNames[i]))
       {
         ConfigTag.setTabNo(i);
         return;
       }

     throw new RuntimeException("Unknown tab name: " + tabName);
  }


  /****************************************************************************/
  protected static XmlDetails m_Instance = null;


  /****************************************************************************/
  /* Maps tab number to a list of the tags which appear on that tab. */

  protected HashMap<Integer, ArrayList<ConfigTag.Generic>> m_PerTabTagTable = new HashMap<Integer, ArrayList<ConfigTag.Generic>>();


  /****************************************************************************/
  /* Maps tag name to tag. */

  protected HashMap<String, ConfigTag.Generic> m_OverallTagTable = new HashMap<String, ConfigTag.Generic>(200);


  /****************************************************************************/
  protected String m_ValidationErrors = "";





  /****************************************************************************/
  /****************************************************************************/
  /**                                                                        **/
  /**                               Private                                  **/
  /**                                                                        **/
  /****************************************************************************/
  /****************************************************************************/

  /****************************************************************************/
  /* The path to the root of the production folder.  This _may_ be passed on
   * the command line when the program is invoked, in which case this gives
   * a convenient place in which to store it.
   */

  String m_ProductionRootPath = "";
  
  
  /****************************************************************************/
  /* List of tag names, in the order the tags were added.  Names are semicolon-
   * separated.  The list may possibly contain leading and / or trailing
   * semicolons, and may also contain multiple contiguous semicolons. */

  private String m_TagList = "";
}
